// ds18b20.c
#include "ds18b20.h"
#include "main.h" // 确保包含主项目头文件以访问 GPIO 引脚定义

#define fac_us 72   // 时钟频率，单位为MHz，对应的微秒转换系数

/* 微秒级延时函数 */
void delay_us(uint32_t nus) {
    uint32_t ticks;
    uint32_t told, tnow, tcnt = 0;
    uint32_t reload = SysTick->LOAD;            // 读取SysTick定时器的初始值
    ticks = nus * fac_us;                       // 计算需要的节拍数，根据输入微秒数计算所需延时时钟节拍数
    told = SysTick->VAL;                        // 记录当前SysTick定时器的计数器值
    while (1) {
        tnow = SysTick->VAL;                    // 获取当前SysTick计数器的值
        if (tnow != told) {                     // 如果当前计数器值发生变化
            if (tnow < told)                    // 如果当前值小于之前值，说明计数器递减到零并重新加载
                tcnt += told - tnow;            // 计算实际经过的时间节拍数
            else
                tcnt += reload - tnow + told;   // 处理计数器值重新加载后的情况
            told = tnow;                        // 更新记录的计数器值
            if (tcnt >= ticks) break;           // 如果已计数的节拍数大于或等于需要的节拍数，退出循环
        }
    }
}

/* DS18B20的数据引脚模式设置函数 */
void Ds18b20_Write_Out_Input(uint8_t cmd) // 设置引脚输入输出模式
{
    GPIO_InitTypeDef GPIO_InitStruct;

    if (cmd) // cmd为1时，设置为输出模式
    {
        GPIO_InitStruct.Pin = DS18B20_Pin;
        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速模式
        GPIO_InitStruct.Pull = GPIO_NOPULL; // 根据实际接线选择上拉或下拉
        HAL_GPIO_Init(DS18B20_GPIO_Port, &GPIO_InitStruct); // 初始化GPIO引脚
    }
    else // cmd为0时，设置为输入模式
    {
        GPIO_InitStruct.Pin = DS18B20_Pin;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 普通输入模式
        GPIO_InitStruct.Pull = GPIO_NOPULL; // 根据实际接线选择上拉或下拉
        HAL_GPIO_Init(DS18B20_GPIO_Port, &GPIO_InitStruct); // 初始化GPIO引脚
    }
}

/* DS18B20复位函数，检测是否存在DS18B20传感器 */
uint8_t Ds18b20_Reset(void)
{
    uint8_t data;

    Ds18b20_Write_Out_Input(1);   // 设置为输出模式
    HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_Pin, GPIO_PIN_RESET); // 拉低数据线，开始复位信号
    delay_us(480);                // 低电平保持480微秒，发送复位信号
    HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_Pin, GPIO_PIN_SET); // 拉高数据线
    delay_us(60);                 // 延时60微秒
    Ds18b20_Write_Out_Input(0);   // 设置为输入模式，准备读取传感器的响应信号
    data = HAL_GPIO_ReadPin(DS18B20_GPIO_Port, DS18B20_Pin); // 读取数据引脚状态，判断传感器是否响应
    delay_us(420);                // 延时420微秒

    return data;                  // 返回传感器的响应结果，0表示存在传感器
}

/* 向DS18B20写入一个字节数据 */
void Ds18b20_Write_Byte(uint8_t data) {
    for (uint8_t i = 0; i < 8; i++) { // 循环写入8位数据
        Ds18b20_Write_Out_Input(1);   // 设置为输出模式
        HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_Pin, GPIO_PIN_RESET); // 拉低数据线
        delay_us(2);                  // 延时2微秒，准备写入数据
        if (data & 0x01)              // 判断最低位是否为1
            HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_Pin, GPIO_PIN_SET); // 如果为1，则拉高数据线
        else
            HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_Pin, GPIO_PIN_RESET); // 如果为0，则保持低电平
        delay_us(60);                 // 延时60微秒，保持数据稳定
        HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_Pin, GPIO_PIN_SET); // 拉高数据线，准备写入下一位

        data >>= 1;                   // 右移一位，准备写入下一位数据
    }
}

/* 从DS18B20读取一个字节数据 */
uint8_t Ds18b20_Read_Byte(void) {
    uint8_t data = 0;
    for (uint8_t i = 0; i < 8; i++) { // 循环读取8位数据
        data >>= 1;                   // 右移一位，准备存储读取的数据位
        Ds18b20_Write_Out_Input(1);   // 设置为输出模式
        HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_Pin, GPIO_PIN_RESET); // 拉低数据线
        delay_us(2);                  // 延时2微秒，准备读取数据
        HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_Pin, GPIO_PIN_SET); // 拉高数据线
        Ds18b20_Write_Out_Input(0);   // 设置为输入模式，读取数据位
        if (HAL_GPIO_ReadPin(DS18B20_GPIO_Port, DS18B20_Pin) == GPIO_PIN_SET) { // 读取引脚电平
            data |= 0x80;             // 如果为高电平，将数据的最高位置1
        }
        delay_us(60);                 // 延时60微秒，保持稳定
    }
    return data;                      // 返回读取的字节数据
}

/* 读取DS18B20温度值，返回浮点数温度值 */
float Ds18b20_Read_Temp(void) {
    uint8_t MSB = 0, MSL = 0;
    uint16_t Temp = 0;
    float temp = 0;

    if (Ds18b20_Reset() == RESET) {   // 先复位传感器，判断是否存在
        Ds18b20_Reset();              // 再次复位传感器
        Ds18b20_Write_Byte(0XCC);     // 跳过ROM操作，选择所有传感器
        Ds18b20_Write_Byte(0X44);     // 启动温度转换
        HAL_Delay(750);               // 延时750毫秒，等待温度转换完成
        Ds18b20_Reset();              // 再次复位传感器
        Ds18b20_Write_Byte(0XCC);     // 跳过ROM操作
        Ds18b20_Write_Byte(0XBE);     // 读取暂存寄存器中的温度值

        MSL = Ds18b20_Read_Byte();    // 读取温度低8位
        MSB = Ds18b20_Read_Byte();    // 读取温度高8位
        Temp = MSB;                   // 将高8位存储到Temp中
        Temp = Temp << 8 | MSL;       // 将低8位与高8位合并，得到完整的温度值

        if ((Temp & 0xF800) == 0xF800) { // 判断温度是否为负值
            temp = (((~Temp) + 0x10) * -0.0625f); // 如果是负值，则取反加一并乘以-0.0625得到实际温度
        } else {
            temp = Temp * 0.0625f;    // 如果是正值，则直接乘以0.0625得到实际温度
        }
        return temp;                  // 返回温度值
    } else
        return 0;                     // 如果没有检测到传感器，返回0
}
